/** 
 * Copyright 2010 Erlacher Felix, Estgfaeller Wolfgang, Ferula Patrick
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package at.socialconference.backend.connection;

import java.io.EOFException;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.Socket;
import java.util.Hashtable;
import java.util.List;

import org.junit.Test;

import at.socialconference.backend.model.Conference;
import at.socialconference.backend.model.Insertable;
import at.socialconference.backend.model.Login;
import at.socialconference.backend.model.Place;
import at.socialconference.backend.model.Talk;
import at.socialconference.backend.model.User;
import at.socialconference.backend.persistence.DBController;
import at.socialconference.inter.connection.protocol.SocketProtocol;


/**
 * Executor for Requests via SocketConnection
 * @author Patrick Ferula, Felix Erlacher, Wolfgang Estgfäller
 */
public class RequestExecutor extends Thread {

	private Socket socket;

	private ObjectInputStream in;
	private ObjectOutputStream out;

	private Integer userid;
	private Integer securityLevel;

	/**
	 * 
	 * @param socket Socket
	 */
	public RequestExecutor(Socket socket) {
		this.socket = socket;
	}

	public void run() {
		try {
			out = new ObjectOutputStream(socket.getOutputStream());
			in = new ObjectInputStream(socket.getInputStream());
			System.out.println("Thread running");
			if (checkLogin() == SocketProtocol.OK) {
				while (!socket.isClosed()) {
					System.out.print("waiting for command");
					String command = in.readUTF();
					System.out.println("<<< " + command);

					if (command.equals(SocketProtocol.CMD_QRCODE)) {
						decodeQRCode();
					} else if (command.equals(SocketProtocol.CMD_CONFERENCENOW)) {
						getConferenceNow();
					} else if (command.equals(SocketProtocol.CMD_SEARCHUSER)) {
						searchUser();
					} else if (command.equals(SocketProtocol.CMD_GETUSER)) {
						int id = in.readInt();
						getUser(id);
					} else if (command.equals(SocketProtocol.CMD_REGISTER)) {
						register();
					} else if (command.equals(SocketProtocol.CMD_REGISTER)) {
						checkFriendStatus();
					} else if (command.equals(SocketProtocol.CMD_FRIENDREQUEST)) {
						friendRequest();
					} else if (command.equals(SocketProtocol.CMD_ADDTWITTERTOKENS)) {
						addTwitterTokens();
					} else if (command.equals(SocketProtocol.CMD_GETTWITTERTOKENS)) {
						getTwitterTokens();
					} else if (command.equals(SocketProtocol.CMD_CHECKFRIENDSTATUS)) {
						checkFriendStatus();
					} else if (command.equals(SocketProtocol.CMD_GETFRIENDREQUESTS)) {
						getFriendRequests();
					}  else if (command.equals(SocketProtocol.CMD_GETCONFERENCES)) {
						getConferences();
					} else if (command.equals(SocketProtocol.CMD_UPDATE_OBJECT)) {
						updateObject();
					} else if (command.equals(SocketProtocol.CMD_DELETE_OBJECT)) {
						deleteObject();
					} else if (command.equals(SocketProtocol.CMD_INSERT_OBJECT)) {
						insertObject();
					} else if (command.equals(SocketProtocol.CMD_PARTICIPATESCHECK)) {
						participatesCheck();
					} else if (command.equals(SocketProtocol.CMD_ENROLLCONFERENCE)) {
						enroll();
					} else if (command.equals(SocketProtocol.CMD_ACCEPT_FRIENDREQUEST)) {
						acceptFriendRequest();
					} else if (command.equals(SocketProtocol.CMD_REJECT_FRIENDREQUEST)) {
						rejectFriendRequest();
					} else if (command.equals(SocketProtocol.CMD_GETPLACES)) {
						getPlaces();
					}else if (command.equals(SocketProtocol.CMD_GETTALKS)) {
						getTalks();
					} else if (command.equals(SocketProtocol.CMD_GOODBYE)) {
						break;
					}else{
						System.out.println("unknown Command");
					}
					out.flush();
				}

			}
			System.out.println("\nende");
			socket.close();
		} catch (EOFException eof) {
			System.out.println("\nunexpected closing");
		} catch (Exception e) {
			e.printStackTrace();
		}
	}







	/**
	 * Accepts a friend request
	 * returns OK (1) if ok
	 * 		FSTATUS_NOFRIENDSHIP if no relation exists (400)
	 * 		FSTATUS_FRIEND if friendship already exists (402)
	 * @throws IOException 
	 */
	private void acceptFriendRequest() throws IOException {
		int fid = in.readInt();
		out.writeInt(DBController.acceptFriendRequest(userid.intValue(), fid));
	}
	
	/**
	 * Rejects a friend request
	 * returns OK (1) if ok
	 * 		FSTATUS_NOFRIENDSHIP if no relation exists (400)
	 * @throws IOException 
	 */
	private void rejectFriendRequest() throws IOException {
		int fid = in.readInt();
		out.writeInt(DBController.rejectFriendRequest(userid.intValue(), fid));
	}

	private void getConferences() throws IOException {

		List<at.socialconference.inter.model.Conference> interConferences;

		List<Conference> backendConferences = DBController.getConferences();

		interConferences = Conference.getInterConferences(backendConferences);

		out.writeObject(interConferences);

		out.flush();
	}
	
	private void getTalks() throws IOException{

		List<at.socialconference.inter.model.Talk> interTalks;

		List<Talk> backendTalks = DBController.getTalks();

		interTalks = Talk.getInterTalks(backendTalks);

		out.writeObject(interTalks);

		out.flush();
		
	}
	
	private void getPlaces() throws IOException {
		List<at.socialconference.inter.model.Place> interPlaces;

		List<Place> backendPlaces = DBController.getPlaces();

		interPlaces = Place.getInterPlaces(backendPlaces);

		out.writeObject(interPlaces);

		out.flush();
	}

	private void getUser(Integer id) throws IOException {
		User self = DBController.getUser(userid);
		Hashtable<Integer, User> friends = DBController.getFriends(self.getId(), false);

		//IF USER IS HIMSELF: ALL FRIENDS, AND FULL INFO (INCL. SECURITY LEVEL)
		if (id == -1) {
			at.socialconference.inter.model.User u = self.generateInterUser(User.LEVEL_HIGH);
			u.setSecurityLevel(securityLevel);
			for (User f : friends.values()) {
				u.addContact(f.generateInterUser(User.LEVEL_MEDIUM));
			}
			out.writeObject(u);
			out.flush();
			return;
		} 

		User u = DBController.getUser(id);

		if (friends.containsKey(id)) {
			out.writeObject(u.generateInterUser(User.LEVEL_MEDIUM));
		} else {
			out.writeObject(u.generateInterUser(User.LEVEL_LOW));
		}
		out.flush();
	}

	/**
	 * Retrieves insertable Object from InputStream
	 * @return insertable Object
	 */
	private Insertable readInsertableObject() {
		try {
			Object object = in.readObject();
			Insertable insertableObject = null;
			if (object instanceof at.socialconference.inter.model.User)
				insertableObject = new User((at.socialconference.inter.model.User) object);
			else if (object instanceof at.socialconference.inter.model.Conference)
				insertableObject = new Conference((at.socialconference.inter.model.Conference) object);
			else if (object instanceof at.socialconference.inter.model.Talk)
				insertableObject = new Talk((at.socialconference.inter.model.Talk) object);
			else if (object instanceof at.socialconference.inter.model.Place)
				insertableObject = new Place((at.socialconference.inter.model.Place) object);
			return insertableObject;
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Updates insertable Object into database
	 * returns OK (1) if ok
	 * 		   WRONG OBJECT (501)
	 * @throws IOException 
	 */
	private void updateObject() throws IOException {
		//TODO: which user can update what?
		Insertable o = readInsertableObject();
		if (o != null) {
			DBController.updateObject(o);
		} else {
			out.writeInt(SocketProtocol.RESP_WRONG_OBJECT);
		}
		out.writeInt(SocketProtocol.OK);
	}

	/**
	 * Inserts new Object into database
	 * returns OK (1) if ok
	 * 		   WRONG OBJECT (501)
	 * @throws IOException 
	 */
	private void insertObject() throws IOException {
		Insertable o = readInsertableObject();
		if (o != null) {
			DBController.insertObject(o);
		} else {
			out.writeInt(SocketProtocol.RESP_WRONG_OBJECT);
		}
		out.writeInt(SocketProtocol.OK);
	}

	/**
	 * Deletes certain Object from Database
	 * returns OK (1) if ok
	 * 		   WRONG OBJECT (501)
	 * @throws IOException 
	 */
	private void deleteObject() throws IOException {
		Insertable o = readInsertableObject();
		if (o != null) {
			DBController.deleteObject(o);
		} else {
			try {
				out.writeInt(SocketProtocol.RESP_WRONG_OBJECT);
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		out.writeInt(SocketProtocol.OK);
	}

	/**
	 * Registers new User
	 * << User
	 * << Login
	 * --
	 * >> USERNAME ALREADY IN DB 301
	 * or
	 * >> NO FIRST NAME 302
	 * or
	 * >> NO LAST NAME 303
	 * or
	 * >> NO USER NAME 304
	 * or
	 * >> NO PASSWORD 305
	 * or
	 * >> OK 1 
	 * @throws Exception
	 */
	private void register() throws Exception {

		at.socialconference.inter.model.Login login = (at.socialconference.inter.model.Login) in.readObject();
		at.socialconference.inter.model.User user = (at.socialconference.inter.model.User) in.readObject();
		if (user.getFirstname().trim().equals("")){
			out.writeInt(SocketProtocol.RESP_NOFIRSTNAME);
			return;
		}
		if (user.getLastname().trim().equals("")){
			out.writeInt(SocketProtocol.RESP_NOLASTNAME);
			return;
		}
		if (login.getUser().trim().equals("")){
			out.writeInt(SocketProtocol.RESP_NOUSERNAME);
			return;
		}
		if (login.getPwd().trim().equals("")){
			out.writeInt(SocketProtocol.RESP_NOPASSWORD);
			return;
		}
		if (DBController.existsLogin(login.getUser())) {
			out.writeInt(SocketProtocol.RESP_DUPLOGIN);
			return;
		} 	


		User u = new User(user);
		DBController.addUser(u);
		Login l = new Login(login, u.getId(), 1);
		DBController.addLogin(l);
		out.writeInt(SocketProtocol.OK);

	}

	/**
	 * Inserts Friend Request into DB
	 * if Friend Request already in DB happens nothing
	 * >> ID of User
	 * << OK 1
	 * @throws Exception
	 */
	private void friendRequest() throws Exception {
		int fid = in.readInt();
		DBController.addFriend(userid.intValue(), fid);
		out.writeInt(SocketProtocol.OK);
	}

	/**
	 * Checks FriendStatus
	 * >> ID of User
	 * << RESP_FSTATUS_YOURSELF 403 if friendID = userID
	 * or
	 * << RESP_FSTATUS_NOFRIENDSHIP 400 no friendship
	 * << RESP_FSTATUS_PENDING 401 pending
	 * << RESP_FSTATUS_FRIEND 402 already friend of user
	 * @throws Exception
	 */
	private void checkFriendStatus() throws Exception {
		int fid = in.readInt();
		// Wenn selbst
		if (fid == userid) {
			out.writeInt(SocketProtocol.RESP_FSTATUS_YOURSELF);
		} else {
			int i = DBController.checkFriendStatus(userid.intValue(), fid);
			switch (i) {
			case 0:
				out.writeInt(SocketProtocol.RESP_FSTATUS_NOFRIENDSHIP);
				break;
			case 1:
				out.writeInt(SocketProtocol.RESP_FSTATUS_PENDING);
				break;
			case 2:
				out.writeInt(SocketProtocol.RESP_FSTATUS_FRIEND);
				break;
			}

		}

	}
	
	private void participatesCheck() throws IOException {
		int cid = in.readInt();
		boolean retValue = DBController.participatesConference(userid.intValue(),cid);
		out.writeBoolean(retValue);
				
	}
	
	/**
	 * Enrolls conference
	 * returns:
	 *   1  - OK
	 *  -1  - FAILURE
	 * 601 - CONFERENCE JUST ENROLLED
	 * @throws IOException
	 */
	private void enroll() throws IOException {
		int cid = in.readInt();
		
		if(DBController.participatesConference(userid, cid)){
			out.writeInt(SocketProtocol.RESP_ALREADYENROLLED);
			return;
		}
		
		boolean retValue = DBController.enroll(userid,cid);
		
		out.writeInt(retValue?SocketProtocol.OK:SocketProtocol.RESP_FAILURE);
	}


	private void searchUser() throws Exception {
		String firstName = in.readUTF();
		String lastName = in.readUTF();
		int country = in.readInt();

		List<User> results = DBController.searchUser(firstName, lastName, country);
		if (results != null && results.size() > 0) {
			out.writeInt(results.size());
			for (User user : results) {
				//patzo's FAIL
				//out.writeObject(user.generateInterUser(MIN_PRIORITY));
				
				out.writeObject(user.generateInterUser(User.LEVEL_LOW));
			}
		} else {
			out.writeInt(0);
		}
	}

	private void getFriendRequests() throws IOException {
		List<User> friendRequests = DBController.getFriendRequests(userid);
		if (friendRequests != null && friendRequests.size() > 0) {
			out.writeInt(friendRequests.size());
			for (User user : friendRequests) {
				out.writeObject(user.generateInterUser(User.LEVEL_LOW));
				
			}
		} else {
			out.writeInt(0);
		}

	}

	private void getConferenceNow() throws IOException {
		Conference con = DBController.getCurrentConference(userid);
		if (con != null) {
			out.writeObject(con.generateInterConference());
		} else {
			out.writeObject(null);
		}
	}

	private void addTwitterTokens() throws IOException {
		String tw_acc = in.readUTF();
		String tw_acc_secret = in.readUTF();
		int status = DBController.addTwitterTokens(userid, tw_acc, tw_acc_secret);
		out.writeInt(status);
	}

	private void getTwitterTokens() throws IOException {
		User u = DBController.getUser(userid);
		if (u != null) {
			out.writeUTF(u.getTw_access_token());
			out.writeUTF(u.getTw_access_secret());
		}
	}

	/**
	 * Decodes a given QR Codes and returns the concerning Object
	 * for user, talk, conference
	 * @throws IOException
	 */
	private void decodeQRCode() throws IOException {
		String input = in.readUTF();

		String[] s = input.split("#");
		String type = s[0];
		String code = s[1];
		Integer id = new Integer(code);
		int tcode = Integer.parseInt(type);
		if (tcode == 1) { // PEOPLE
			User u = DBController.getUser(id);
			if (u != null) {
				out.writeInt(SocketProtocol.RESP_QR_PEOPLE);
				int ul = getUserLevel(u.getId());
				out.writeObject(u.generateInterUser(ul));
			} else {
				out.writeInt(SocketProtocol.RESP_QR_NOTHING);
			}
		} else if (tcode == 2) { // CONFERENCE
			Conference c = DBController.getConference(id);
			if(c != null){
				out.writeInt(SocketProtocol.RESP_QR_CONFERENCE);
				out.writeObject(c.generateInterConference());	
			}else{
				out.writeInt(SocketProtocol.RESP_QR_NOTHING);
			}
		} else if (tcode == 3) { // TALK
			Talk talk = DBController.getTalk(id);
			if(talk!=null){
				out.writeInt(SocketProtocol.RESP_QR_TALK);
				out.writeObject(talk.generateInterTalk());
			} else {
				out.writeInt(SocketProtocol.RESP_QR_NOTHING);
			}
		}
	}

	private int getUserLevel(Integer id) {

		if(id.equals(userid)) //IF HIMSELF
			return User.LEVEL_HIGH;

		Hashtable<Integer, User> friends = DBController.getFriends(userid, false);

		if(friends.containsKey(id)) //IF FRIEND
			return User.LEVEL_MEDIUM;
		else
			return User.LEVEL_LOW;
	}

	private int checkLogin() throws IOException {
		System.out.print("<<<read user ");
		String username = in.readUTF();
		System.out.print("got '" + username + "'\n<<<read pw");
		String password = in.readUTF();
		System.out.print("got '" + password + "'");

		int resp = -1;
		if (username != null && password != null) {
			if (username.equals("registrationAccount") && password.equals("S0c1@lC0nF3r3nC3")) {
				resp = 1;
				out.writeInt(resp);
				out.flush();
				return resp;
			}
			Login login = DBController.getLogin(username);
			if (login != null) {
				if (login.getPwd().equals(password)) {
					System.out.println(username + " " + login.getUserId() + " connected");
					resp = SocketProtocol.OK;
					userid = login.getUserId();
					securityLevel = login.getSecurityLevel();
				} else {
					resp = SocketProtocol.RESP_WRONG_PW;
				}
			} else {
				resp = SocketProtocol.RESP_NO_LOGIN;
			}
		} else {
			resp = SocketProtocol.RESP_WRONG_INPUT;
		}
		out.writeInt(resp);
		out.flush();
		return resp;
	}

	@Test
	public void testgetQRCode() {
		User u = DBController.getUser(7);
		at.socialconference.inter.model.User user = u.generateInterUser(User.LEVEL_LOW);

		System.out.println(user.getAddress());

	}

}
